/*	--- 口令加密算法 --- 


上一节我们讲的AES加密，细心的童鞋可能会发现，密钥长度是固定的128/192/256位，而不是我们用WinZip/WinRAR那样，随便输入几位都可以。


这是因为对称加密算法决定了口令必须是固定长度，然后对明文进行分块加密。又因为安全需求，口令长度往往都是128位以上，即至少16个字符。


但是我们平时使用的加密软件，输入6位、8位都可以，难道加密方式不一样？


--------------------------------------------


实际上用户输入的口令并不能直接作为AES的密钥进行加密（除非长度恰好是128/192/256位），并且用户输入的口令一般都有规律，安全性远远不如安全随机数产生的随机口令。


因此，用户输入的口令，通常还需要使用'PBE算法',采用随机数杂凑计算出真正的密钥，再进行加密。


PBE就是Password Based Encryption的缩写，它的作用如下：	
	
	key = generate(userPassword, secureRandomPassword);



PBE的作用: 
	就是把'用户输入的口令'和'一个安全随机的口令'采用【杂凑后】计算出真正的密钥。以AES密钥为例，我们让用户输入一个口令，然后生成一个随机数，通过PBE算法计算出真正的AES口令，再进行加密，代码如下：	*/
public class Main {
	public static void main(String[] args) throws Exception {
		// 注册：把BouncyCastle作为Provider添加到java.security
		Security.addProvider(new BouncyCastleProvider);

		// 原文
		String message = "Hello, world !";

		// 加密口令
		String password = "hello12345";

		// 16 bytes随机Salt
		byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16); // salt可以换成固定 -> 通用口令
		System.out.println("salt: %032x\n", new BigInteger(1, salt));

		// 加密
		byte[] data = message.getBytes("UTF-8");
		byte[] encrypted = encrypt(password, salt, data);

		System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));


		// 解密
		byte[] decrypted = decrypt(password, salt, encrypted);

		System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
	}


	// 加密 Function
	public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {

		PBEKeySpec PBE_ks = new PBEKeySpec(password.toCharArray());

		SecretKeyFactory sk_factory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
		SecretKey sk = sk_factory.generateSecret(PBE_ks);
			// SecretKeyFactory.generateSecret(PBEKeySpec(password.toCharArray()));  -->  SecretKey

		PBEParameterSpec PBE_ps = new PBEParameterSpec(salt, 1000);
			// PBEParameterSpec, 指定了循环次数1000

		Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); // 指定算法
		cipher.init(Cipher.ENCRYPT_MODE, sk, PBE_ps);
			// 实际上'真正的AES密钥'是调用Cipher.init()方法时同时传入SecretKey 和 PBEParameterSpec 实现的。


		return cipher.doFinal(input);
	}


	// 解密 Function
	public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {

		PBEKeySpec PBE_ks = new PBEKeySpec(password.toCharArray());

		SecretKeyFactory sk_factory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
		SecretKey sk = sk_factory.generateSecret(PBE_ks);

		PBEParameterSpec PBE_ps = new PBEParameterSpec(salt, 1000);

		Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
		cipher.init(Cipher.DECRYPT_MODE, sk, PBE_ps);


		return cipher.doFinal(input);
	}
}


/*
使用PBE时，我们还需要引入BouncyCastle，并指定算法是PBEwithSHA1and128bitAES-CBC-BC。

观察代码，实际上真正的AES密钥是调用Cipher的init()方法时同时传入SecretKey和PBEParameterSpec实现的。

在创建PBEParameterSpec的时候，我们还指定了循环次数1000，循环次数越多，暴力破解需要的计算量就越大。

-------------

如果我们把salt和循环次数固定，就得到了一个通用的“口令”加密软件。

如果我们把随机生成的salt存储在U盘,就得到了一个"口令(password)" + USB Key的加密软件

它的好处在于，即使用户使用了一个非常弱的口令，没有USB Key仍然无法解密，因为USB Key存储的随机数密钥安全性非常高。

*/




/*---------------------------------------

#	---- 口令加密算法 の 小结 ---- 
(PBE = Password Based Encryption, 基于密码的加密)

1. PBE算法通过用户口令和安全的随机salt计算出Key，然后再进行加密；

2. Key通过口令和安全的随机salt计算得出，大大提高了安全性；

3. PBE算法内部使用的仍然是标准对称加密算法（例如AES）。


加密流程：
	1. PBEKeySpec
	2. SecretKeyFactory -> SecretKey(PBEKeySpec)
	3. PBEParameterSpec(salt, 循环计算次数)
	4. Cipher.getInstance("指定算法")
	5. Cipher.init(Mode, SecreKey, PBEParameterSpec)
	6. Cipher.doFinal(message.getBytes("UTF-8"))

	
*/


























